module vlib.ev.fcgi.Process;

import vlib.Types;

private {
	extern(Windows) {
		int bind(int , void*, int);
                int connect(int, vAddr4*, int) ;
		int socket(int af, int type, int protocol);
	}
	extern(C){
		int fclose(int);
	}
}

struct vProcess {
	static const MaxPathLen	= 512 ;
	alias typeof(*this) This ;
	alias typeof(this) pThis ;
	
	enum State {
		Stoped ,
		Started ,
		Connecting ,
		Working ,
		Closing ,
		Stoping ,
	}
	
	wchar[MaxPathLen]		dir, app , cmd ;
	PROCESS_INFORMATION	pi ;
	STARTUPINFO			psi ;
	SECURITY_ATTRIBUTES	sa;
	Berkeley				soc ;
	bool					_running ;
	bool					_working ;
	vAddr4				_addr ;
	int 					_id ;	
	
	Berkeley	si ;
	int		si_fd = -1 ;
	Berkeley	so ;
	int		so_fd = -1 ;
	bool		_connected ;
	ev_timer	io_timer ;
	ev_io	i_read, i_write ;
	ev_io	o_read, o_write ;
	vBuffer	ob , ib, oo = null ;
	State	_state ;
	bool		is_req_start ;
	
	static int			php_port ;
	static vProcess[]	list ;
	static ev_timer		timer_watcher ;
	static char[]		_env ;
	static char[]	toNullEndedBuffer(char[][char[]] src){
		char[] dest;
		foreach (key, value; src) {
			dest ~= key ~ '=' ~ value ~ '\0';
		}
		dest ~= "\0\0";
		return dest;
	}
	
	static void Init() {
		
		int		size	=  _G.fcgi_max_process * This.sizeof ;
		void*	_ptr	=  cast(void*) GC.calloc( size , GC.BlkAttr.NO_MOVE | GC.BlkAttr.NO_SCAN ) ;
		list			= cast(This[]) _ptr[0 .. size] ;
		assert(list.length is _G.fcgi_max_process );
		
		scope path	= new FilePath( _G.php ) ;
		php_port		= _G.fcgi_port + 1 ;
		
		ubyte[1024 * 32] _env_tmp ;
		scope _env_bu	= new vBuffer(_env_tmp);
		foreach(char[] key, char[] val; _G.env ) {
			_env_bu(key)('=')(val)('\0');
		}
		foreach( char[] _key, _val ; Environment.get ) {
			auto _ptr	= _key in  _G.env ;
			if( _ptr !is null || _key.length is 0 ) {
				continue ;
			}
			// lg("`{}`='{}'", _key, _val);
			_env_bu(_key)('=')(_val)('\0');
		}
		_env_bu("\0\0") ;
		
		_env	= (cast(char[]) _env_bu.slice).dup ;

		foreach( int i, ref p; list) {
			toWDir( p.dir.ptr ,  path.parent);
			toWDir( p.app.ptr , path.toString);
			if( _G.php_args !is null ) {
				toWDir( p.cmd.ptr ,  _G.php_args );
			}
			p._id	= i ;
			p.setup ;
			p.exec() ;
			if( ! p._running ) {
				vLib.Exit(4) ;
			}
		}
		ev_timer_init(&timer_watcher, &On_Closed_Timer,  2, 2);
		ev_timer_start(vLib.loop, &timer_watcher) ;
	}
	
	
	static void accept(ref Berkeley ssoc){
		pThis _this	= null ;
		foreach(ref p; list) {
			if( p._state is State.Started ) {
				ssoc.accept(p.si);
				p.proxy ;
				return ;
			}
		}
		log!(__FILE__,__LINE__)("stop");
		vLib.fcgi.stop ;
	}
	
	void proxy() out {
		assert(ob !is null);
		assert(ib !is null);
		assert(oo !is null);
	} body {
		_state	= State.Connecting ;
		
		if( oo is null ) {
			ob	= new vBuffer(1024, 1024);
			ib	= new vBuffer(1024, 1024);
			oo	= new vBuffer(1024, 1024);
		} else {
			ob.clear ;
			ib.clear ;
			oo.clear ;
		}

		so.open(AddressFamily.INET, SocketType.STREAM, ProtocolType.TCP) ;
		si.blocking( false ) ;
		so.blocking( false ) ;
		
		si_fd		= osf_open(cast(void*) si.handle) ;
		so_fd	= osf_open(cast(void*) so.handle) ;
		
		io_timer.data	= this ;
		i_read.data	= this ;
		i_write.data	= this ;
		o_read.data	= this ;
		o_write.data	= this ;
		
		
		ev_timer_init(&io_timer, &On_Timer,  24, 24) ;
		ev_io_init(&i_read, &On_Req_Read,  si_fd, EV_READ) ;
		ev_io_init(&i_write, &On_Req_Write,  si_fd, EV_WRITE ) ;
		
		ev_io_init(&o_read, &On_Res_Read,  so_fd, EV_READ) ;
		ev_io_init(&o_write, &On_Res_Write,  so_fd, EV_WRITE ) ;
		
		int ret = connect(so.handle, &_addr, _addr.sizeof ) ;
		ev_timer_start(vLib.loop, &io_timer);
		ev_io_start(vLib.loop, &i_read);
		ev_io_start(vLib.loop, &o_write);
		
	}
	
	void setup() {
		tango.stdc.string.memset(&pi, 0, PROCESS_INFORMATION.sizeof) ;
		tango.stdc.string.memset(&si, 0, STARTUPINFO.sizeof );
		psi.cb		= STARTUPINFO.sizeof;
		psi.dwFlags	= STARTF_USESTDHANDLES;
		soc.open(AddressFamily.INET, SocketType.STREAM, ProtocolType.TCP);
		
		_addr._family      = AddressFamily.INET ;  
		static ubyte[4] _ipv	= [127, 0,  0 , 1];
		tango.stdc.string.memcpy(&_addr._ip[0], &_ipv[0], _ipv.length ) ;
		
		while(true) {
			if( php_port >= 65534 ) {
				vLib.Exit(3) ;
			}
			_addr._port	= vlib.util.Byte.htons( php_port ) ;
			int ret		= bind(soc.handle, &_addr, _addr.sizeof) ;
			if( ret !is -1 ) {
				break ;
			}
			php_port++;
		}
		soc.listen(32);
		
		psi.hStdInput	= cast(HANDLE) soc.handle ;
		psi.hStdOutput	= INVALID_HANDLE_VALUE ; 
		psi.hStdError	= INVALID_HANDLE_VALUE ;
		_running		= false ;
	}
	
	void reboot() {
		close() ;
		_state	= State.Stoping ;
		soc.detach ;
		stop() ;
		setup ;
		ev_timer_stop(vLib.loop, &io_timer);
		io_timer.data	= this ;
		ev_timer_set(&io_timer, 0.1, 0.1) ;
		ev_timer_start(vLib.loop, &io_timer);
	}
	
	void exec() {
		auto ret		= CreateProcessW(&app[0], &cmd[0], null, null, true, CREATE_NO_WINDOW, _env.ptr , &dir[0], &psi, &pi ) ;
		if( ret ) {
			_running	= true ;
			CloseHandle(pi.hThread);
			_state	= State.Started ;
			vLib.fcgi.start ;
		} else {
			// log error 
			_running	= false ;
		}
	}
	
	void stop() {
		if (_running) {
			if (TerminateProcess( pi.hProcess, cast(UINT) -1) ) {
				scope(exit) {
					CloseHandle(pi.hProcess);
					_running = false;
				}
				DWORD ret	= WaitForSingleObject(pi.hProcess, INFINITE) ;
				// WAIT_FAILED
				if( ret !is 0 ){
					// error log 
				}
			}
		}
		_state	= State.Stoping ;
	}
	
	static void toWDir(T)(T* ptr, char[] str) {
		tango.stdc.string.memset(ptr, 0,  MaxPathLen * T.sizeof );
		if( str is null || str.length is 0 ) {
			return ;
		}
		T[]	_str	= Utf.fromString8!(T)(str, ptr[0..MaxPathLen]);
		foreach(ref c; _str) {
			if( c is '/' )  c = '\\' ;
		}
	}
	
	static extern(C) void StopAll(){
		if( list !is null && list.length ) foreach( int i, ref p; list) {
			p.stop ;
		}
	}
	
	static extern(C) void On_Closed_Timer(ev_loop_t* loop, ev_timer* w, int ev) {
		static int count	= 1 ;
		foreach( int i, ref p; list) {
			DWORD rc = WaitForSingleObject(p.pi.hProcess, 0) ;
			if( rc !is 258 ) {
				p.stop ;
				p.exec ;
				vLib.fcgi.start ;
			}
		}
	}
	
	void reuse() {
		if( this.ib.readable is 0 && this.ob.readable is 0 ) {
			this.ib.clear;
			ev_io_start(vLib.loop, &i_read) ;
			this.close();
		}
	}
	
	void close() {
		ev_timer_stop(vLib.loop, &io_timer);

		ev_io_stop(vLib.loop, &i_read);
		ev_io_stop(vLib.loop, &i_write);
		ev_io_stop(vLib.loop, &o_read);
		ev_io_stop(vLib.loop, &o_write);
		
		if( si_fd !is -1 ) {
			osf_close(si_fd);
			si.detach ;
			so_fd	= -1 ;
		}
		if( so_fd !is -1 ) {
			osf_close(so_fd);
			so.detach ;
			so_fd	= -1 ;
		}
		_connected	= false ;
		is_req_start	= false ;
		vLib.fcgi.start ;
		_state		= State.Started ;
	}
	
	static extern(C) void On_Timer(ev_loop_t* loop, ev_timer* w, int ev) in {
		assert(w !is null);
		assert(w.data !is null);
	} body {
		pThis	_this	= cast(pThis) w.data ;
		if( _this._state is State.Stoping  ) {
			// restart php 
			_this.exec() ;
		} else {
			// if( _this._state is State.Connecting  ) 
			// restart php 
			_this.reboot() ;
		}
		ev_timer_stop(vLib.loop, &_this.io_timer);
		return ;
	}
	
	static extern(C) void On_Req_Read (ev_loop_t* loop, ev_io* w, int ev) in {
		assert(w !is null);
		assert(w.data !is null);
	} body {
		pThis	_this	= cast(pThis) w.data ;
		log!(__FILE__, __LINE__)("On_Req_Read start, len:{}", _this.ib.readable);
		bool	is_first	= true ;
		ubyte[512] tmp ;
		while( true ) {
			int i	= _this.si.receive(tmp);
			if( i < 0 || i > tmp.length ) {
				log!(__FILE__, __LINE__)("read:{}:`{}`", i, _this.ib.readable );
				/*
				if( _this.is_req_start ) {
					_this.close() ;
					return ;
				}
				_this.is_req_start	= true ;
				*/
				break ;
			}
			if( is_first ) {
				is_first	= false ;
				if( i is 0 ) {
					log!(__FILE__, __LINE__)("close, read:`{}`", i, _this.ib.readable );
					_this.close() ;
					return ;
				}
			}
			if( i is 0 ) {
				break ;
			}
			_this.ib(tmp[0..i]) ;
		}
		log!(__FILE__, __LINE__)("On_Req_Read end, len:{}", _this.ib.readable);
		if( _this.ib.readable ) {
			ev_io_start(vLib.loop, &_this.o_write) ;
		}
		ev_timer_stop(vLib.loop, &_this.io_timer);
		ev_timer_start(vLib.loop, &_this.io_timer);
	}
	
	static extern(C) void On_Req_Write(ev_loop_t* loop, ev_io* w, int ev) in {
		assert(w !is null);
		assert(w.data !is null);
	} body {
		pThis	_this	= cast(pThis) w.data ;
		log!(__FILE__, __LINE__)("On_Req_Write start, len:{}",  _this.ob.readable );
		if( _this.ob.readable is 0 ) {
			ev_io_stop(vLib.loop, &_this.i_write) ;
			return ;
		}
		bool	is_write	= false ;
		ubyte[] _tmp	= cast(ubyte[]) _this.ob.slice ;
		while(true) {
			int i	= _this.si.send(  _tmp ) ;
			if( i > _tmp.length || i < 0 ) {
				log!(__FILE__, __LINE__)("close");
				_this.reboot() ;
				return ;
			}
			if( i is 0 ) {
				break ;
			}
			is_write	= true ;
			if(  i is _tmp.length ) {
				_tmp	= _tmp[0..0] ;
				break ;
			}
			_tmp	= _tmp[i..$] ;
		}
		log!(__FILE__, __LINE__)("On_Req_Write end:`{}:{}`", _this.ob.readable - _tmp.length,  _tmp.length );
		_this.ob.clear;
		if( _tmp.length ) {
			_this.oo(_tmp);
			_this.ob( _this.oo.slice ) ;
			_this.oo.clear ;
		} else {
			ev_io_stop(vLib.loop, &_this.i_write) ;
			_this.reuse();
		}
		if( is_write ) {
			//ev_io_stop(vLib.loop, &_this.o_read) ;
		}
		ev_timer_stop(vLib.loop, &_this.io_timer);
		ev_timer_start(vLib.loop, &_this.io_timer);
	}
	
	static extern(C) void On_Res_Read (ev_loop_t* loop, ev_io* w, int ev) in {
		assert(w !is null);
		assert(w.data !is null);
	} body {
		pThis	_this	= cast(pThis) w.data ;
		log!(__FILE__, __LINE__)("On_Res_Read start, len:{}", _this.ob.readable);
		bool	is_first	= true ;
		ubyte[512] tmp ;
		while( true ) {
			int i	= _this.so.receive(tmp);
			if( i < 0 || i > tmp.length ) {
				ev_io_stop(vLib.loop, &_this.o_read) ;
				//ev_io_start(vLib.loop, &_this.i_read) ;
				log!(__FILE__, __LINE__)("read:{}:{}", i, _this.ob.readable );
				break ;
			}
			if( is_first ) {
				is_first	= false ;
				if( i is 0 ) {
					log!(__FILE__, __LINE__)("close");
					_this.reboot() ;
					return ;
				}
			}
			if( i is 0 ) {
				break ;
			}
			_this.ob(tmp[0..i]) ;
		}
		log!(__FILE__, __LINE__)("On_Res_Read end, {}",  _this.ob.readable );
		if( _this.ob.readable ) {
			ev_io_stop(vLib.loop, &_this.o_read) ;
			ev_io_start(vLib.loop, &_this.i_write) ;
		}
		ev_timer_stop(vLib.loop, &_this.io_timer);
		ev_timer_start(vLib.loop, &_this.io_timer);
	}
	
	static extern(C) void On_Res_Write(ev_loop_t* loop, ev_io* w, int ev) in {
		assert(w !is null);
		assert(w.data !is null);
	} body {
		pThis	_this	= cast(pThis) w.data ;
		log!(__FILE__, __LINE__)("On_Res_Write start, len:{}", _this.ib.readable);
		if( _this._state is State.Connecting  ) {
			_this._state	= State.Working ;
		}
		if( _this.ib.readable is 0 ) {
			ev_io_stop(vLib.loop, &_this.o_write) ;
			return ;
		}
		ubyte[] _tmp	= cast(ubyte[]) _this.ib.slice ;
		while(true) {
			int i	= _this.so.send(  _tmp ) ;
			if( i > _tmp.length || i < 0 ) {
				log!(__FILE__, __LINE__)("close");
				_this.reboot() ;
				return ;
			}
			if(  i is _tmp.length ) {
				_tmp	= _tmp[0..0] ;
				break ;
			}
			if( i is 0 || i is _tmp.length ) {
				break ;
			}
			_tmp	= _tmp[i..$] ;
		}
		log!(__FILE__, __LINE__)("On_Res_Write end:`{}:{}`", _this.ib.readable - _tmp.length,  _tmp.length );
		_this.ib.clear;
		if( _tmp.length ) {
			_this.oo(_tmp);
			_this.ib( _this.oo.slice ) ;
			_this.oo.clear ;
		} else {
			ev_io_stop(vLib.loop, &_this.o_write) ;
			ev_io_start(vLib.loop, &_this.o_read) ;
		}
		
		ev_timer_stop(vLib.loop, &_this.io_timer);
		ev_timer_start(vLib.loop, &_this.io_timer);
	}
}

struct PROCESSENTRY32W {
	DWORD dwSize;
	DWORD cntUsage;
	DWORD th32ProcessID;
	DWORD th32DefaultHeapID;
	DWORD th32ModuleID;
	DWORD cntThreads;
	DWORD th32ParentProcessID;
	LONG pcPriClassBase;
	DWORD dwFlags;
	WCHAR szExeFile[MAX_PATH];
}

extern(Windows){
	HANDLE CreateToolhelp32Snapshot(DWORD, DWORD);
	BOOL Process32FirstW(HANDLE, PROCESSENTRY32W*);
	BOOL Process32NextW(HANDLE, PROCESSENTRY32W*);
	const TH32CS_SNAPPROCESS  = 0x2 ;
}

void TerminateOldProcess() {
	PROCESSENTRY32W pe32;
	pe32.dwSize	= pe32.sizeof;
	HANDLE hProcessSnap	= CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0u);
	if( hProcessSnap == INVALID_HANDLE_VALUE ){
		return ;
	}
	scope(exit){
		CloseHandle(hProcessSnap);
	}
	int i	= rfind(_G.php, '/');
	if( i is 0 || i is _G.php.length ) {
		i	=  rfind(_G.php, '\\');
	}
	if( i is 0 || i is _G.php.length ) {
		return ;
	}
	char[] _php	= _G.php[i+1..$] ;
	wchar[512] tmp ;
	wchar[] __php	= Utf.toString16(_php, tmp);
	
	uint[512]	_pid ;
	int		_pid_i ;
	BOOL bMore	= Process32FirstW(hProcessSnap, &pe32);
	while(bMore){
		int	j = find(pe32.szExeFile, '\0');
		if(  pe32.szExeFile[0..j] == __php ) {
			_pid[ _pid_i++]	= pe32.th32ProcessID ;
		}                  
		bMore	=	Process32NextW(hProcessSnap, &pe32);
	}
	if( _pid_i is 0 ){
		return ;
	}
	while(_pid_i--) {
		HANDLE   _handle	=OpenProcess(PROCESS_ALL_ACCESS,TRUE,  _pid[_pid_i]);
		DWORD   _code	=0;
		TerminateProcess(_handle, _code);
		CloseHandle(_handle);
	}
	Thread.sleep(0.3) ;
}
